Skip to content

Add support for AWS Nitro - #45

Open
ameba23 wants to merge 12 commits into
mainfrom
peg/nitro
Open

Add support for AWS Nitro#45
ameba23 wants to merge 12 commits into
mainfrom
peg/nitro

Conversation

@ameba23

@ameba23 ameba23 commented May 20, 2026

Copy link
Copy Markdown
Collaborator

This adds AWS Nitro attestation support to attested-tls.

It adds a new attestation type AwsNitro which AttestationGenerator and AttestationVerifier will use to create and verify Nitro attestation documents.

The implementation uses the nsm-nitro-enclave-utils crate for NSM interaction and COSE/CBOR parsing.

The AWS root certificate is hardcoded as DER. It will expire on 2049-10-28 and was retrieved from https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip

Attestation detection now checks for Nitro by attempting communication with the Nitro Security Module at /dev/nsm. This is checked before attempting TDX DCAP attestation and only when the nitro feature is present.

For running tests outside of a Nitro enclave, mock Nitro attestations are produced which verify against a mock root of trust rather than AWS.

Nitro support is gated behind the nitro feature flag. But it is enabled by default. Happy to disable it by default is others prefer.

Timestamp checks

Nitro attestation docs have a signed timestamp field. We can verify that this matches up with the not before / not after times on the TLS cert. In attested-tls crate, the not-before time is read immediately before attestation is generated (so that it can be included as input).

I have not yet added this check here, because it would be an API breaking change, needing to pass in a time-window to the attestation verifier fn. But if we were serious about adopting this, i would add it in a followup PR.

Testing on a deployment

For testing on a Nitro deployment, this pairs with flashbots/attested-tls-proxy#162 which adds vsocket support to attestation-provider-server allowing us to check that these nitro attestation documents will verify.

There is also a flake for reproducibly building a docker container with attestation-provider-server: https://github.com/flashbots/attested-tls-proxy/blob/peg/nitro-test/flake.nix

Heres how to deploy using that flake (from the attested-tls-proxy repo on the paired branch):

  1. Build the OCI image from the flake in attested-tls-proxy:
nix build ".#attestation-provider-server-image"

This produces a Docker image tarball at result in the repo root.

  1. Scp that image to the nitro host, and load into Docker:
docker load < result
  1. Build the EIF from the loaded image:
nitro-cli build-enclave \
  --docker-uri attestation-provider-server:latest \
  --output-file attestation-provider-server.eif
  1. Run the enclave:
nitro-cli run-enclave \
  --eif-path attestation-provider-server.eif \
  --cpu-count 2 \
  --memory 512 \
  --enclave-cid 10 \
  --enclave-name attestation-provider-server

The flake bakes in the enclave startup command, so the container starts the server in vsock mode and with --server-attestation-type aws-nitro by default. That means you do not need to pass extra runtime flags when building the EIF.

The server side then listens on vsock port 8000, and the parent host can connect with the client command using:

attestation-provider-server client \
  --server-transport vsock \
  --server-cid 10 \
  --log-dcap-quote \
  --server-vsock-port 8000

Which should verify the attestation-document, and dump it to a local file. But not actually check measurements.

This shows that nitro attestations can be verified but does not demonstrate an attested-tls workflow with nitro. flashbots/attested-tls-proxy#163 adds full vsocket support to attested-tls-proxy allowing us to test a full attested-tls workflow on Nitro.

@ameba23 ameba23 changed the title Add experimental support for AWS Nitro Add support for AWS Nitro Jun 10, 2026
@ameba23
ameba23 marked this pull request as ready for review June 11, 2026 09:31
@ameba23
ameba23 requested a review from 0x416e746f6e June 12, 2026 09:14
samlaf added a commit to SeismicSystems/attested-tls that referenced this pull request Aug 31, 2026
AttestationResult sat at the boundary that is meant to hide which
platform produced the evidence, and two of its three fields were Intel
types: a dcap-qvl Quote and a QuoteCollateralV3. The AWS Nitro work in
flashbots#45 has neither, so the struct would break a second time the moment a
non-TDX platform lands. flashbots#65 breaks it with no new platform at all - it
moves the collateral into the evidence, so the verifier fetches none.

The parsed quote leaves the shared type. It had exactly one consumer:
AttestationVerifier binds it only to hand to the GCP provenance check,
which reads the PPID out of the PCK leaf. gcp/firmware.rs discards it,
and so does our own downstream. dcap.rs still hands it back, now as the
second element of a tuple, so nothing re-parses the same bytes
(34346c6) and the module gains no public type of its own - flashbots#40 may
replace that module with attest-verify, which already owns a
ValidatedDcapQuote one letter away.

CollateralSnapshot becomes EndorsementSnapshot, and the bundle becomes
an optional field on it. What a verifier fetches varies; the instant it
holds that material to does not. So the instant stays a plain field and
the fetched material hangs off it, with #[non_exhaustive] and a
::dcap() constructor keeping the pairing structural and later fields
additive. A struct rather than an enum keyed by platform, because a
verification consumes a set of fetched material rather than one of
several alternatives, and because the axis it varies along is not the
platform: whether endorsements ride in the evidence or get fetched is a
transport choice of the protocol. flashbots#49 already made that choice one way
for the Azure AK chain, and flashbots#65 proposes the opposite for DCAP
collateral.

"Collateral" is Intel's DCAP word, and would read as a category error
the moment the snapshot holds Azure vTPM roots or whatever Nitro needs.
"Endorsements" is the loose umbrella rather than RFC 9334's strict term
- a DCAP bundle spans Endorsements and Reference Values both - and the
doc comment says so, so the imprecision is deliberate rather than
sloppy.

The type is called VerifiedAttestation again, which reverses the rename
in the commit before this one. That rename argued the value is the far
end of one appraisal. It is not: the appraisal policy runs after the
value is built, and a caller can configure it to check nothing, which
is exactly what our enclave does before appraising in its own admission
predicate. So no Reference Value comparison is implied, and what comes
back is verified evidence rather than an Attestation Result. Leaving the
RFC's name unspent keeps it for the type that would earn it if appraisal
is ever separated from verification. Result is also taken in Rust, with
AttestationError in this same crate.

The docs on both types are shorter for it. The cache-refresh caveat is
stated once instead of three times, and where the value sits in the RATS
pipeline is stated once, linked to RFC 9334.

Addresses review feedback on flashbots#85.

BREAKING CHANGE: AttestationResult is renamed VerifiedAttestation and
loses its quote field. CollateralSnapshot is renamed
EndorsementSnapshot, is #[non_exhaustive], and its collateral field
becomes dcap: Option<QuoteCollateralV3>, built through
EndorsementSnapshot::dcap.
The dcap::verify_* functions return (VerifiedAttestation, Quote), the
azure::verify_* functions return VerifiedAttestation, and
AttestationVerifier::verify_attestation{,_sync} return
Option<VerifiedAttestation>.
samlaf added a commit to SeismicSystems/attested-tls that referenced this pull request Aug 31, 2026
AttestationResult sat at the boundary that is meant to hide which
platform produced the evidence, and two of its three fields were Intel
types: a dcap-qvl Quote and a QuoteCollateralV3. The AWS Nitro work in
flashbots#45 has neither, so the struct would break a second time the moment a
non-TDX platform lands. flashbots#65 breaks it with no new platform at all - it
moves the collateral into the evidence, so the verifier fetches none.

The parsed quote leaves the shared type. It had exactly one consumer:
AttestationVerifier binds it only to hand to the GCP provenance check,
which reads the PPID out of the PCK leaf. gcp/firmware.rs discards it,
and so does our own downstream. dcap.rs still hands it back, now as the
second element of a tuple, so nothing re-parses the same bytes
(34346c6) and the module gains no public type of its own - flashbots#40 may
replace that module with attest-verify, which already owns a
ValidatedDcapQuote one letter away.

CollateralSnapshot becomes EndorsementSnapshot, and the bundle becomes
an optional field on it. What a verifier fetches varies; the instant it
holds that material to does not. So the instant stays a plain field and
the fetched material hangs off it, with #[non_exhaustive] and a
::dcap() constructor keeping the pairing structural and later fields
additive. A struct rather than an enum keyed by platform, because a
verification consumes a set of fetched material rather than one of
several alternatives, and because the axis it varies along is not the
platform: whether endorsements ride in the evidence or get fetched is a
transport choice of the protocol. flashbots#49 already made that choice one way
for the Azure AK chain, and flashbots#65 proposes the opposite for DCAP
collateral.

"Collateral" is Intel's DCAP word, and would read as a category error
the moment the snapshot holds Azure vTPM roots or whatever Nitro needs.
"Endorsements" is the loose umbrella rather than RFC 9334's strict term
- a DCAP bundle spans Endorsements and Reference Values both - and the
doc comment says so, so the imprecision is deliberate rather than
sloppy.

The type is called VerifiedAttestation again, which reverses the rename
in the commit before this one. That rename argued the value is the far
end of one appraisal. It is not: the appraisal policy runs after the
value is built, and a caller can configure it to check nothing, which
is exactly what our enclave does before appraising in its own admission
predicate. So no Reference Value comparison is implied, and what comes
back is verified evidence rather than an Attestation Result. Leaving the
RFC's name unspent keeps it for the type that would earn it if appraisal
is ever separated from verification. Result is also taken in Rust, with
AttestationError in this same crate.

The docs on both types are shorter for it. The cache-refresh caveat is
stated once instead of three times, and where the value sits in the RATS
pipeline is stated once, linked to RFC 9334.

Addresses review feedback on flashbots#85.

BREAKING CHANGE: AttestationResult is renamed VerifiedAttestation and
loses its quote field. CollateralSnapshot is renamed
EndorsementSnapshot, is #[non_exhaustive], and its collateral field
becomes dcap: Option<QuoteCollateralV3>, built through
EndorsementSnapshot::dcap.
The dcap::verify_* functions return (VerifiedAttestation, Quote), the
azure::verify_* functions return VerifiedAttestation, and
AttestationVerifier::verify_attestation{,_sync} return
Option<VerifiedAttestation>.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant